#!/usr/bin/env perl
#
# Copyright 2006-2024 Roy Hills
#
# This file is part of arp-scan.
#
# arp-scan is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# arp-scan is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with arp-scan.  If not, see <http://www.gnu.org/licenses/>.
#
# arp-fingerprint -- Perl script to fingerprint system with arp-scan
#
# Author: Roy Hills
# Date: 30th May 2006
#
# This script uses arp-scan to fingerprint the operating system on the
# specified target.
#
# It sends various different ARP packets to the target, and records which
# ones it responds to.  From this, it constructs a fingerprint string
# which is used to match against a hash containing known fingerprints.
#
use warnings;
use strict;
use Getopt::Std;
#
sub get_localnet($);
#
my $arpscan="arp-scan -q -r 1 -x";
#
# Hash of known fingerprints
#
# These fingerprints were observed on:
#
# FreeBSD 14.0	FreeBSD 14.0-RELEASE-p3 amd64 on VMware
# FreeBSD 13.1	FreeBSD 13.1-RELEASE-p9 amd64 on VMware
# FreeBSD 12.0	FreeBSD 12.0-RELEASE amd64 on VMware
# FreeBSD 11.2	FreeBSD 11.2-RELEASE amd64 on VMware
# FreeBSD 10.3	FreeBSD 10.3 amd64 on VMware
# FreeBSD 9.1	FreeBSD 9.1 i386 on VMware
# FreeBSD 8.2	FreeBSD 8.2 i386 on VMware
# FreeBSD 7.0	FreeBSD 7.0 i386 on VMware
# FreeBSD 5.3	FreeBSD 5.3 i386 on VMware
# FreeBSD 4.3	FreeBSD 4.3 i386 on VMware
# DragonflyBSD 2.0	Dragonfly BSD 2.0.0 i386 on VMware
# DragonflyBSD 3.0	Dragonfly BSD 3.0.2 i386 on VMware
# DragonflyBSD 3.2	Dragonfly BSD 3.2.2 amd64 on VMware
# DragonflyBSD 4.6	Dragonfly BSD 4.6.0 amd64 on VMware
# Win 3.11	Windows for Workgroups 3.11/DOS 6.22 on VMware
# 95		Windows 95 OSR2 on VMware
# Win98		Windows 98 SE on VMware
# WinME		Windows ME on VMware
# Windows7	Windows 7 Professional 6.1.7600 Build 7600 on Dell Vostro 220
# Windows8	Windows 8 Pro x64 6.2.9200 Build 9200 on VMware
# Windows10	Windows 10 Pro 10.0.14393 Build 14393 on VMware
# Windows11	Windows 11 Pro 10.0.22621 on VMware
# NT 3.51	Windows NT Server 3.51 SP0 on VMware
# NT4		Windows NT Workstation 4.0 SP6a on Pentium
# 2000		Windows 2000
# XP		Windows XP Professional SP2 on Intel P4
# 2003		Windows 2003 Server SP1 on Intel P4
# Vista		Windows Vista Beta 2 Build 5384 on VMware
# Vista		Windows Vista SP1 Build 6001 on Dell Inspiron
# 2008		Windows 2008 Server Beta on i386
# 2012R2	Windows Server 2012R2 x64 on HP proliant server
# 2022		Windows Server 2022 x64 on VMware
# Linux 2.0	Linux 2.0.29 on VMware (debian 1.3.1)
# Linux 2.2	Linux 2.2.19 on VMware (debian potato)
# Linux 2.4	Linux 2.4.29 on Intel P3 (debian sarge)
# Linux 2.6	Linux 2.6.15.7 i686 on Intel P3 (debian sarge)
# Linux 2.6	Kindle 3.1 on Amazon Kindle 3
# Linux 2.6	Linux 2.6.32.60 x86_64 on VMware (debian squeeze)
# Linux 3.2	Linux 3.2.0 686 on VMware (debian wheezy)
# Linux 3.8	Linux 3.8.8 x86_64 on VMware (fedora 17)
# Linux 4.0	Linux 4.0.6 x86_64 on VMware (fedora 22)
# Linux 4.6	Linux 4.6.7 x86_64 on VMware (fedora 24)
# Cisco IOS	IOS 11.2(17) on Cisco 2503
# Cisco IOS	IOS 11.3(11b)T2 on Cisco 2503
# Cisco IOS	IOS 12.0(8) on Cisco 1601
# Cisco IOS	IOS 12.1(27b) on Cisco 2621
# Cisco IOS	IOS 12.2(32) on Cisco 1603
# Cisco IOS	IOS 12.3(15) on Cisco 2503
# Cisco IOS	IOS 12.4(3) on Cisco 2811
# Cisco IOS	IOS 12.4(24)T1 on Cisco 1841
# Cisco IOS	IOS 15.0(1)M on Cisco 7206 (dynamips)
# Solaris 2.5.1	Solaris 2.5.1 (SPARC) on Sun SPARCstation 20
# Solaris 2.6	Solaris 2.6 (SPARC) on Sun Ultra 5
# Solaris 7	Solaris 7 (x86) on VMware
# Solaris 8	Solaris 8 (SPARC) on Sun Ultra 5 (64 bit)
# Solaris 9	Solaris 9 (SPARC) on Sun Ultra 5 (64 bit)
# Solaris 10	Solaris 10 (x86) on VMware
# ScreenOS 5.0	Juniper ScreenOS 5.0.0r9 on NetScreen 5XP
# ScreenOS 5.1	Juniper ScreenOS 5.1.0r1.0 on NetScreen 5GT
# ScreenOS 5.3	Juniper ScreenOS 5.3.0r4.0 on NetScreen 5GT
# ScreenOS 5.4	Juniper ScreenOS 5.4.0r1.0 on NetScreen 5GT
# ScreenOS 5.4	Juniper ScreenOS 5.4.0r22.0 on NetScreen 5GT
# ScreenOS 6.2	Juniper ScreenOS 6.2.0r12.0 on Juniper SSG5
# MacOS 10.4	MacOS 10.4.6 on powerbook G4
# MacOS 10.3	MacOS 10.3.9 on imac G3
# IRIX 6.5	IRIX64 IRIS 6.5 05190004 IP30 on SGI Octane
# SCO OS 5.0.7	SCO OpenServer 5.0.7 on VMware
# 2.11BSD	2.11BSD patch level 431 on PDP-11/73 (SIMH simulated)
# 4.3BSD	4.3BSD (Quasijarus0c) on MicroVAX 3000 (SIMH simulated)
# OpenBSD 3.1	OpenBSD 3.1 i386 on VMware
# OpenBSD 3.9	OpenBSD 3.9 i386 on VMware
# OpenBSD 4.8	OpenBSD 4.8 i386 on VMware
# OpenBSD 5.1	OpenBSD 5.1 amd64 on VMware
# OpenBSD 5.9	OpenBSD 5.9 amd64 on VMware
# NetBSD 2.0.2	NetBSD 2.0.2 i386 on VMware
# NetBSD 4.0	NetBSD 4.0 i386 on VMware
# NetBSD 5.1	NetBSD 5.1.2 i386 on VMware
# NetBSD 6.0	NetBSD 6.0.1 amd64 on VMware
# NetBSD 7.0	NetBSD 7.0.1 amd64 on VMware
# IPSO 3.2.1	IPSO 3.2.1-fcs1 on Nokia VPN 210
# Netware 6.5	Novell NetWare 6.5 on VMware
# HP-UX 11	HP-UX B.11.00 A 9000/712 (PA-RISC)
# PIX OS	PIX OS (unknown vsn) on Cisco PIX 525
# PIX OS 4.4	PIX OS 4.4(4) on Cisco PIX 520
# PIX OS 5.1	PIX OS 5.1(2) on Cisco PIX 520
# PIX OS 5.2	PIX OS 5.2(9) on Cisco PIX 520
# PIX OS 5.3	PIX OS 5.3(2) on Cisco PIX 520
# PIX OS 6.0	PIX OS 6.0(4) on Cisco PIX 520
# PIX OS 6.1	PIX OS 6.1(5) on Cisco PIX 520
# PIX OS 6.2	PIX OS 6.2(4) on Cisco PIX 520
# PIX OS 6.3	PIX OS 6.3(5) on Cisco PIX 520
# PIX OS 7.0(1)	PIX OS 7.0(1) on Cisco PIX 515E
# PIX OS 7.0(2)	PIX OS 7.0(2) on Cisco PIX 515E
# PIX OS 7.0(4)	PIX OS 7.0(4) on Cisco PIX 515E
# PIX OS 7.0(6)	PIX OS 7.0(6) on Cisco PIX 515E
# PIX OS 7.1	PIX OS 7.1(1) on Cisco PIX 515E
# PIX OS 7.2	PIX OS 7.2(1) on Cisco PIX 515E
# PIX OS 8.0	PIX OS 8.0(2) on Cisco PIX 515E
# Minix 3	Minix 3 1.2a on VMware
# Nortel Contivity 6.00	Nortel Contivity V06_00 (VxWorks based)
# Nortel Contivity 6.05	Nortel Contivity V06_05.135
# AIX 4.3	IBM AIX Version 4.3 on RS/6000 7043-260
# AIX 5.3	IBM AIX Version 5.3 on RS/6000 7043-260
# Cisco VPN Concentrator 4.7	Cisco VPN Concentrator 3030 4.7.2E
# Cisco IP Phone 79xx SIP 5.x,6.x,7.x	7940 SIP firmware version 5.3
# Cisco IP Phone 79xx SIP 5.x,6.x,7.x	7940 SIP firmware version 6.3
# Cisco IP Phone 79xx SIP 5.x,6.x,7.x	7940 SIP firmware version 7.5
# Cisco IP Phone 79xx SIP 8.x	7940 SIP firmware version 8.6
# Catalyst 1900	Cisco Catalyst 1900 V9.00.03 Standard Edition
# Catalyst IOS 12.2	Cisco Catalyst 3550-48 running IOS 12.2(35)SE
# Catalyst IOS 12.0	Cisco Catalyst 2924-XL running IOS 12.0(5)WC17
# Catalyst IOS 12.1	Cisco Catalyst 3550-48 running IOS 12.1(11)EA1a SMI
# FortiOS 3.00	FortiGate 100A running FortiOS 3.00,build0406,070126
# Plan9		Plan9 release 4 on VMware
# Blackberry OS	Blackberry OS v5.0.0.681 on Blackberry 8900
# GNU/Hurd	Debian GNU/Hurd (GNU-Mach 1.3.99/Hurd-0.3) on VMware
# BeOS		BeOS 5.0.3 PE Max on VMware
# RiscOS 5.19	RiscOS 5.19 on Raspberry Pi
# WIZnet W5100	WIZnet W5100 on Ethernet chip on Arduino Ethernet shield
# Android 4.1	Android 4.1.2 on Samsung Galaxy S3 Mini (wifi)
# Android 4.4	Android 4.4.2 on Google Nexus 7 (wifi)
#
my %fp_hash = (
   '11110100000' => 'FreeBSD 5.3, 7.0, 8.2, 9.1, 10.3, 11.2, DragonflyBSD 2.0, 3.0, 3.2, 4.6, Win98, WinME, NT4, 2000, XP, 2003, Catalyst IOS 12.0, 12.1, 12.2, FortiOS 3.00',
   '01000100000' => 'Linux 2.2, 2.4, 2.6',
   '01010100000' => 'Linux 2.2, 2.4, 2.6, 3.2, 3.8, 4.0, 4.6, Vista, 2008, 2012R2, 2022, Windows7, Windows8, Windows10, Windows11', # Linux only if non-local IP is routed
   '00000100000' => 'Cisco IOS 11.2, 11.3, 12.0, 12.1, 12.2, 12.3, 12.4, 15.0',
   '11110110000' => 'Solaris 2.5.1, 2.6, 7, 8, 9, 10, HP-UX 11, NetBSD 6.0, 7.0',
   '01000111111' => 'ScreenOS 5.0, 5.1, 5.3, 5.4, 6.2',
   '11110000000' => 'Linux 2.0, MacOS 10.4, IPSO 3.2.1, Minix 3, Cisco VPN Concentrator 4.7, Catalyst 1900, BeOS, WIZnet W5100, FreeBSD 12.0, FreeBSD 13.1, FreeBSD 14.0',
   '11110100011' => 'MacOS 10.3, FreeBSD 4.3, IRIX 6.5, AIX 4.3, AIX 5.3',
   '10010100011' => 'SCO OS 5.0.7',
   '10110100000' => 'Win 3.11, 95, NT 3.51',
   '11110000011' => '2.11BSD, 4.3BSD, OpenBSD 3.1, 3.9, 4.8, 5.1, 5.9, Nortel Contivity 6.00, 6.05, RiscOS 5.19',
   '10110110000' => 'NetBSD 2.0.2, 4.0, 5.1',
   '10110111111' => 'PIX OS 4.4, 5.1, 5.2, 5.3, Android 4.1',
   '11110111111' => 'PIX OS 6.0, 6.1, 6.2, ScreenOS 5.0 (transparent), Plan9, Blackberry OS',
   '00010110011' => 'PIX OS 6.3, 7.0(1), 7.0(2)',
   '01010110011' => 'PIX OS 7.0(4)-7.0(6), 7.1, 7.2, 8.0',
   '00000110000' => 'Netware 6.5',
   '00010100000' => 'Unknown 1', # 14805 79.253 Cisco
   '00000110011' => 'Cisco IP Phone 79xx SIP 5.x,6.x,7.x',
   '11110110011' => 'Cisco IP Phone 79xx SIP 8.x', # Also 14805 63.11 Fujitsu Siemens
   '01010000000' => 'GNU/Hurd, Android 4.4',
   );
#
my $usage =
qq/Usage: arp-fingerprint [options] <target>
Fingerprint the target system using arp-scan.

'options' is one or more of:
        -h Display this usage message.
        -v Give verbose progress messages.
	-o <option-string> Pass specified options to arp-scan
	-l Fingerprint all targets in the local net.
	-m Include the MAC address of the target in the output
/;
my %opts;
my $user_opts="";
my $verbose;
my $fingerprint="";
my $fp_name;
my @targets;
my $target;
my $show_mac;
my $mac_address;
#
# Process options
#
die "$usage\n" unless getopts('hlvmo:',\%opts);
if ($opts{h}) {
   print "$usage\n";
   exit(0);
}
$verbose=$opts{v} ? 1 : 0;
if ($opts{o}) {
   $user_opts = $opts{o};
}
$show_mac=$opts{m} ? 1 : 0;

#If we're working in localnet mode, we don't need arguments
if ($#ARGV != 0 && !$opts{l}) {
   die "$usage\n";
}

if ($opts{l}) {
   @targets=get_localnet($user_opts);
} else {
   @targets=@ARGV;
}

for $target (@targets) {
   $fingerprint="";
#
# Check that the target is not an IP range or network.
#
   if ($target =~ /\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+/ ||
       $target =~ /\d+\.\d+\.\d+\.\d+\/\d+/ ||
       $target =~ /\d+\.\d+\.\d+\.\d+:\d+\.\d+\.\d+\.\d+/) {
      die "argument must be a single IP address or hostname\n";
   }
#
# Check that the system responds to an arp-scan with no options.
# If it does, then fingerprint the target.
#
   $mac_address = &fp("","$target");
   if ($mac_address ne "0") {
# 1: source protocol address = localhost
      $fingerprint .= &fp("--arpspa=127.0.0.1","$target");
# 2: source protocol address = zero
      $fingerprint .= &fp("--arpspa=0.0.0.0","$target");
# 3: source protocol address = broadcast
      $fingerprint .= &fp("--arpspa=255.255.255.255","$target");
# 4: source protocol address = non local (network 1 is reserved)
      $fingerprint .= &fp("--arpspa=1.0.0.1","$target");	# Non-local source IP
# 5: invalid arp opcode
      $fingerprint .= &fp("--arpop=255","$target");
# 6: arp hardware type = IEEE_802.2
      $fingerprint .= &fp("--arphrd=6","$target");
# 7: invalid arp hardware type
      $fingerprint .= &fp("--arphrd=255","$target");
# 8: invalid arp protocol type
      $fingerprint .= &fp("--arppro=0xffff","$target");
# 9: arp protocol type = Novell IPX
      $fingerprint .= &fp("--arppro=0x8137","$target");
# 10: invalid protocol address length
      $fingerprint .= &fp("--arppln=6","$target");
# 11: Invalid hardware address length
      $fingerprint .= &fp("--arphln=8","$target");
#
      if (defined $fp_hash{$fingerprint}) {
         $fp_name = "$fp_hash{$fingerprint}";
      } else {
         $fp_name = "UNKNOWN";
      }
      if ($show_mac) {
         print "$target\t$mac_address\t$fingerprint\t$fp_name\n";
      } else {
         print "$target\t$fingerprint\t$fp_name\n";
      }
   } else {
      print "$target\tNo Response\n";
   }
}
#
# Scan the specified IP address with arp-scan using the given options.
# If the options are empty, return the MAC address of the target, otherwise
# return "1" if the target responds, or "0" if it does not respond.
#
sub fp ($$) {
   my $ip;
   my $options;
   my $response = "0";
   ($options, $ip) = @_;

   open(ARPSCAN, "$arpscan $user_opts $options $ip |") || die "arp-scan failed";
   while (<ARPSCAN>) {
      if (/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\t([0-9a-f:]+)$/) {
         if ($options eq "") {
            $response = $1;	# MAC address from arp-scan output
         } else {
            $response = "1";
         }
         last;
      }
   }
   close(ARPSCAN);

   if ($verbose && $options ne "") {
      if ($response) {
         print "$options\tYes\n";
      } else {
         print "$options\tNo\n";
      }
   }

   return $response;
}

#
# use -l flag on arp-scan to collect all IPs in the local network
#
sub get_localnet($) {
   my $user_opts = $_[0];
   my @targets;

   open(ARPSCAN, "$arpscan $user_opts -l |") || die "arp-scan failed";
   while (<ARPSCAN>) {
      if (/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\t/) {
         push @targets, $1;
      }
   }
   close(ARPSCAN);

   die "parse of arp-scan failed" unless @targets;
   return @targets;
}
